﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using Needle.Bus.Configuration;
using Needle.Utils.Extensions;

namespace Needle.Bus.Engine.Factories
{

    public class BusConfigurationFactory : IDisposable, IFactory<BusConfiguration>
    {

        private const string OneLiteral = "1";
        private const string IdAttribute = "id";
        private const string TrueLiteral = "true";
        private const string TypeAttribute = "type";
        private const string NamespacePrefix = "ns";
        private const string DefaultFilter = "default";
        private const string NameAttribute = "name";
        private const string BatchAttribute = "batch";
        private const string FilterAttribute = "filter";
        private const string HandlerAttribute = "handler";
        private const string OnDemandAttribute = "onDemand";
        private const string MinBatchSizeAttribute = "minBatchSize";
        private const string MaxBatchSizeAttribute = "maxBatchSize";
        private const string GetPropertiesXPathQuery = "ns:property";
        private const string GetBusNamesXPathQuery = "/ns:needle/ns:bus/@id";
        private const string GetSingleBusConfigXPathQuery = "/ns:needle/ns:bus[@id='{0}']";
        private const string GetStagesXPathQuery = "/ns:needle/ns:bus[@id='{0}']/ns:stages/ns:stage";
        private const string GetMessageHandlersXPathQuery = "/ns:needle/ns:bus[@id='{0}']/ns:messageHandlers/ns:messageHandler";
         
        private Stream _data;

        /// <summary>
        /// Initializes a new instance of the BusConfigurationLoader class.
        /// </summary>
        /// <param name="data"></param>
        public BusConfigurationFactory(Stream data)
        {
            if (data == null)
                throw new ArgumentNullException("data", "data is null.");
            
            int bytesRead = 0;
            byte[] buffer = new byte[4.KiloByte()];
            MemoryStream ms = new MemoryStream(4.KiloByte());
            do
            {
                bytesRead = data.Read(buffer, 0, buffer.Length);
                if (bytesRead > 0)
                    ms.Write(buffer, 0, bytesRead);
            } while (bytesRead > 0);
            _data = ms;
        }

        public BusConfiguration Create(string id)
        {
            if (_data.Position != 0)
            {
                _data.Position = 0;
            }
            XmlDocument doc = new XmlDocument();
            XmlSchema schema=null;
            using (var schemaStream = typeof(BusConfiguration).Assembly.GetManifestResourceStream(typeof(BusConfiguration), "bus.configuration.xsd"))
            {
                schema = XmlSchema.Read(schemaStream, null);
            }

            doc.Schemas.Add(schema);
            doc.Load(_data);
            doc.Validate(null);

            XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable);
            nsMgr.AddNamespace(NamespacePrefix, BusConfiguration.XMLNamespace);

            XmlNode busConfig = doc.SelectSingleNode(String.Format(GetSingleBusConfigXPathQuery, id), nsMgr);

            if (busConfig == null)
                throw new ArgumentException(String.Format("No bus configuration with id '{0}' found.", id));

            var config = new BusConfiguration(busConfig.Attributes[IdAttribute].Value);

            LoadStages(doc, nsMgr, config);
            LoadMessageHandlers(doc, nsMgr, config);
            return config;
        }

        private static void LoadStages(XmlDocument doc, XmlNamespaceManager nsMgr, BusConfiguration config)
        {
            XmlNodeList nodes = doc.SelectNodes(string.Format(GetStagesXPathQuery, config.Id), nsMgr);
            foreach (XmlNode node in nodes)
            {
                var stageConfig = new StageConfiguration();

                stageConfig.Id = node.Attributes[IdAttribute].Value;
                stageConfig.Handler = node.Attributes[HandlerAttribute].Value;

                XmlAttribute filterAttr = node.Attributes[FilterAttribute];
                if (filterAttr != null)
                    stageConfig.Filter = filterAttr.Value;
                else
                    stageConfig.Filter = DefaultFilter;

                XmlAttribute onDemandAttr = node.Attributes[OnDemandAttribute];
                if (onDemandAttr != null)
                    stageConfig.StartOnDemand = (onDemandAttr.Value == TrueLiteral || onDemandAttr.Value == OneLiteral);
                else
                    stageConfig.StartOnDemand = true;

                XmlAttribute batchAttr = node.Attributes[BatchAttribute];
                if (batchAttr != null)
                {
                    bool batchMode = false;
                    if (batchAttr.Value == TrueLiteral || batchAttr.Value == OneLiteral)
                        batchMode = true;
                    stageConfig.BatchMode = batchMode;
                }

                XmlAttribute minBatchSizeAttr = node.Attributes[MinBatchSizeAttribute];
                if (minBatchSizeAttr != null)
                {
                    int minBatchSize  = 0;
                    if (int.TryParse(minBatchSizeAttr.Value, out minBatchSize))
                        stageConfig.MinBatchSize = minBatchSize;
                    else
                        minBatchSize = 1;
                }

                XmlAttribute maxBatchSizeAttr = node.Attributes[MaxBatchSizeAttribute];
                if (maxBatchSizeAttr != null)
                {
                    int maxBatchSize = 0;
                    if (int.TryParse(maxBatchSizeAttr.Value, out maxBatchSize))
                        stageConfig.MaxBatchSize = maxBatchSize;
                    else
                        maxBatchSize = int.MaxValue;
                }

                config.Stages.Add(stageConfig);
            }
        }

        private static void LoadMessageHandlers(XmlDocument doc, XmlNamespaceManager nsMgr, BusConfiguration config)
        {
            XmlNodeList nodes = doc.SelectNodes(string.Format(GetMessageHandlersXPathQuery, config.Id), nsMgr);
            foreach (XmlNode node in nodes)
            {
                var mhConfig = new MessageHandlerConfiguration();

                mhConfig.Id = node.Attributes[IdAttribute].Value;
                mhConfig.Type = node.Attributes[TypeAttribute].Value;
                foreach (XmlNode dataNode in node.SelectNodes(GetPropertiesXPathQuery, nsMgr))
                {
                    mhConfig.Data[dataNode.Attributes[NameAttribute].Value] = dataNode.InnerXml;
                }
                config.MessageHandlers.Add(mhConfig);
            }
        }

        public void Dispose()
        {
            if (_data != null)
                _data.Dispose();
        }

        public IList<BusConfiguration> CreateAll()
        {
            throw new NotImplementedException();
        }
    }
}
